package time

import (
	"../array_kit"
	"../common"
	"fmt"
	"reflect"
	"strconv"
	"time"
)

const (
	Equal = iota
	Greater
	Littler
	GreaterEqual
	LittlerEqual
)

const dateFormat = "2006-01-02"
const timeFormat = "2006-01-02 15:04:05"
const Day = time.Duration(24) * time.Hour

func InterfaceIsNil(val interface{}) (flag bool) {
	if !reflect.ValueOf(val).IsValid() {
		return true
	}
	if IsReferenceType(reflect.ValueOf(val)) && reflect.ValueOf(val).IsNil() {
		return true
	}
	return
}

func IsReferenceType(value reflect.Value) bool {
	switch value.Kind() {
	case reflect.Ptr, reflect.Chan, reflect.Interface, reflect.Func, reflect.Map, reflect.Slice:
		return true
	default:
		return false
	}
}

func ToString(obj interface{}) string {
	if InterfaceIsNil(obj) {
		return ""
	}
	switch reflect.TypeOf(obj).Kind() {
	default:
		return fmt.Sprintf("%v", obj)
	case reflect.Float32, reflect.Float64:
		return fmt.Sprintf("%g", obj)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Int8, reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
		return fmt.Sprintf("%d", obj)
	}
}

func StringToInt(str string) int {
	num, _ := strconv.Atoi(str)
	return num
}

func TimeFormatByStr(timeValueStr string, isgame bool) (time.Time, error) {
	if !isgame {
		return time.ParseInLocation(timeFormat, timeValueStr, time.Local)
	}
	return time.Parse(timeFormat, timeValueStr)
}

func TimeFormatToStr(time_ *time.Time) string {
	return time_.Format(timeFormat)
}

func TimeUnixStr(tarTime time.Time) string {
	return strconv.FormatInt(tarTime.Unix(), 10)
}

func TimeNowAndBeforeDays(before int) (timeStart string, timeEnd string) {
	if before <= 0 {
		panic("befor should be more 0")
	}
	timeNow := time.Now()
	timeStart = timeNow.Format(timeFormat)
	beforeDay := -1 * before
	timeEnd = timeNow.AddDate(0, 0, beforeDay).Format(timeFormat)
	return
}

func GetNow() *time.Time {
	now := time.Now()
	return &now
}

//ConvertTimestampToStr compatible with timestamp json string
func ConvertTimestampToStr(timeStamp float64) string {
	return strconv.FormatInt(int64(timeStamp), 10)
}

//ConvertTimestampToStr compatible with timestamp json string
func ConvertTimestampToStr2(timeStamp int) string {
	return strconv.FormatInt(int64(timeStamp), 10)
}

func FormateTime(t *time.Time) string {
	return t.Format(timeFormat)
}

func FormateDate(t *time.Time) string {
	return t.Format(dateFormat)
}

func FormateTime2(t time.Time) string {
	return t.Format(timeFormat)
}

func FormateDate2(t time.Time) string {
	return t.Format(dateFormat)
}

func TimeStampToTime(timeStamp int64) *time.Time {
	temp := time.Unix(timeStamp, 0)
	return &temp
}

func TimeStampToStr(timeStamp int64) string {
	return time.Unix(timeStamp, 0).Format(timeFormat)
}

func TimeStampToStrYMD(timeStamp int64) string {
	return time.Unix(timeStamp, 0).Format(dateFormat)
}

func TimeStampStrToStr(timeStampStr string) string {
	timeStamp, _ := strconv.ParseInt(timeStampStr, 10, 64)
	return time.Unix(timeStamp, 0).Format(timeFormat)
}

func TimeStampStrToTime(timeStampStr string) *time.Time {
	timeStamp, _ := strconv.ParseInt(timeStampStr, 10, 64)
	temp := time.Unix(timeStamp, 0)
	return &temp
}

func TimeStrToTime(timeStr string) *time.Time {
	temp, err := time.Parse(timeFormat, timeStr)
	if err == nil {
		return &temp
	}
	return nil
}

func DateStrToTime(timeStr string) *time.Time {
	temp, err := time.Parse(dateFormat, timeStr)
	if err == nil {
		return &temp
	}
	return nil
}

func TimeStrToDate(timeStr string) *time.Time {
	temp, err := time.Parse(dateFormat, timeStr)
	if err == nil {
		return &temp
	}
	return nil
}

func TimeByTimeZone(time_ *time.Time) time.Time {
	cstZone := time.FixedZone("CST", 8*3600) //cst:central standard time
	return time_.In(cstZone)
}

func TimeCompare(time1, time2 *time.Time) int {
	switch {
	case time1 == nil && time2 != nil:
		return Littler
	case time1 != nil && time2 == nil:
		return Greater
	case time1 == nil && time2 == nil:
		return Equal
	default:
		return TimeCompareByLocation(time1, time2)
	}
}

type ITimeRangeCheck interface {
	TimeRangeCheck(timeStart, timeEnd, defaultTimeStart, defaultTimeEnd *time.Time) (from, to *time.Time, isValid bool)
}

type TimeRangeCheckFunc func(timeStart, timeEnd, defaultTimeStart, defaultTimeEnd *time.Time) (from, to *time.Time, isValid bool)

func (timeRangeCheckFunc TimeRangeCheckFunc) TimeRangeCheck(timeStart, timeEnd, defaultTimeStart, defaultTimeEnd *time.Time) (from, to *time.Time, isValid bool) {
	return timeRangeCheckFunc(timeStart, timeEnd, defaultTimeStart, defaultTimeEnd)
}

func SearchOpTimeRange(timeStart, timeEnd, defaultTimeStart, defaultTimeEnd *time.Time, itimeRangeCheckFunc ITimeRangeCheck) (from, to *time.Time, isValid bool) {
	return itimeRangeCheckFunc.TimeRangeCheck(timeStart, timeEnd, defaultTimeStart, defaultTimeEnd)
}

func MonthsBetweenTimsRange(timeStrat, timeEnd *time.Time, defaultMonthGap int) []string {
	switch {
	case timeStrat == nil && timeEnd != nil:
		timeStrat = timeEnd
	case timeEnd == nil && timeStrat != nil:
		timeEnd = timeStrat
	case timeStrat == nil && timeEnd == nil:
		tempEnd, tempStart := time.Now(), time.Now().AddDate(0, defaultMonthGap, 0)
		timeStrat, timeEnd = &tempStart, &tempEnd
	}
	timeStratStrYm, timeEndStrYm := StringToInt(GetDateYearMonth(timeStrat)), StringToInt(GetDateYearMonth(timeEnd))
	months := []string{}
	for i := timeEndStrYm; i >= timeStratStrYm; i-- {
		if i%100 == 0 {
			i = (i/100-1)*100 + 12
		}
		months = append(months, ToString(i))
	}
	return months
}

func GetDateYearMonth(date *time.Time) string {
	month := int(date.Month())
	year := date.Year() % 100
	monthStr := ToString(month)
	if len(monthStr) == 1 {
		monthStr = "0" + monthStr
	}
	return ToString(year) + monthStr
}

func GetDateYearMonth2(date *time.Time, seperator string) string {
	year := date.Year()
	month := int(date.Month())
	monthStr := ToString(month)
	if len(monthStr) == 1 {
		monthStr = "0" + monthStr
	}
	return ToString(year) + seperator + monthStr
}

func GetYearMonthDayHour(date time.Time, seperator string) string {
	month := int(date.Month())
	monthStr := ToString(month)
	dayStr := ToString(date.Day())
	yearStr := ToString(date.Year())
	HourStr := ToString(date.Hour())
	if len(monthStr) == 1 {
		monthStr = "0" + monthStr
	}
	if len(dayStr) == 1 {
		dayStr = "0" + dayStr
	}
	if len(HourStr) == 1 {
		HourStr = "0" + HourStr
	}
	return yearStr + seperator + monthStr + seperator + dayStr + seperator + HourStr
}

func TimeByLocation(timeStr string) *time.Time {
	local, _ := time.LoadLocation("Local")
	starttime, _ := time.ParseInLocation(timeFormat, timeStr, local)
	return &starttime
}

func DateByLocation(dateStr string) *time.Time {
	local, _ := time.LoadLocation("Local")
	date, _ := time.ParseInLocation(dateFormat, dateStr, local)
	return &date
}

func TimeCompareByLocation(time1, time2 *time.Time) int {
	local, _ := time.LoadLocation("Local")
	time1_, _ := time.ParseInLocation(timeFormat, FormateTime(time1), local)
	time2_, _ := time.ParseInLocation(timeFormat, FormateTime(time2), local)
	temp := time1_.Sub(time2_)
	switch {
	case temp > 0:
		return Greater
	case temp < 0:
		return Littler
	default:
		return Equal
	}
}

func TimeStrCompareByLocation(time1, time2 string) int {
	local, _ := time.LoadLocation("Local")
	time1_, _ := time.ParseInLocation(timeFormat, FormateTime(TimeStrToTime(time1)), local)
	time2_, _ := time.ParseInLocation(timeFormat, FormateTime(TimeStrToTime(time2)), local)
	temp := time1_.Sub(time2_)
	switch {
	case temp > 0:
		return Greater
	case temp < 0:
		return Littler
	default:
		return Equal
	}
}

//!!!!! return original value if exception !!!! if obj is map,and key of map is string
func ConvertTimeStToStr(obj interface{}, tagOfStructField string) interface{} {
	if InterfaceIsNil(obj) {
		return obj
	}
	result := map[string]interface{}{}
	objV := reflect.Indirect(reflect.ValueOf(obj))
	if t, ok := (objV.Interface()).(time.Time); ok {
		return FormateTime2(t)
	}
	switch objV.Kind() {
	default:
		return obj
	case reflect.Array, reflect.Slice:
		temp := []interface{}{}
		for i := 0; i < objV.Len(); i++ {
			if objV.Index(i).CanInterface() {
				temp = append(temp, ConvertTimeStToStr(objV.Index(i).Interface(), tagOfStructField))
			}
		}
		return temp
	case reflect.Map:
		newObjMap := map[string]interface{}{}
		if objV.Type().Key().Kind() != reflect.String {
			keys := objV.MapKeys()
			for i := 0; i < len(keys); i++ {
				if keys[i].CanInterface() && objV.MapIndex(keys[i]).CanInterface() {
					newObjMap[fmt.Sprintf("%v", keys[i].Interface())] = objV.MapIndex(keys[i]).Interface()
				}
			}
			return ConvertTimeStToStr(newObjMap, tagOfStructField)
		}
		keys := objV.MapKeys()
		for i := 0; i < len(keys); i++ {
			ivalueV := objV.MapIndex(keys[i])
			if !ivalueV.IsValid() && ivalueV.IsZero() {
				result[keys[i].String()] = nil
				continue
			}
			if ivalueV.Type().Kind() == reflect.Ptr && (!ivalueV.IsValid() || ivalueV.IsZero()) {
				result[keys[i].String()] = nil
				continue
			}
			if !ivalueV.CanInterface() {
				continue
			}
			if InterfaceIsNil(ivalueV.Interface()) {
				result[keys[i].String()] = nil
				continue
			}
			ivalue := reflect.Indirect(ivalueV).Interface()
			if t, ok := ivalue.(time.Time); ok {
				result[keys[i].String()] = FormateTime2(t)
				continue
			}
			if reflect.Indirect(reflect.ValueOf(ivalueV.Interface())).Type().Kind() == reflect.Struct {
				result[keys[i].String()] = ConvertTimeStToStr(ivalue, tagOfStructField)
				continue
			}
			if reflect.Indirect(reflect.ValueOf(ivalueV.Interface())).Type().Kind() == reflect.Map {
				result[keys[i].String()] = ConvertTimeStToStr(ivalue, tagOfStructField)
				continue
			}
			if reflect.Indirect(reflect.ValueOf(ivalueV.Interface())).Type().Kind() == reflect.Slice ||
				reflect.Indirect(reflect.ValueOf(ivalueV.Interface())).Type().Kind() == reflect.Array {
				iarray := reflect.Indirect(reflect.ValueOf(ivalueV.Interface()))
				temp := []interface{}{}
				for i := 0; i < iarray.Len(); i++ {
					if iarray.Index(i).CanInterface() {
						temp = append(temp, ConvertTimeStToStr(iarray.Index(i).Interface(), tagOfStructField))
					}
				}
				result[keys[i].String()] = temp
				continue
			}
			result[keys[i].String()] = ivalue
		}
		return result
	case reflect.Struct:
		temp, _ := common.FieldValueToTagValue(obj, tagOfStructField, false)
		return ConvertTimeStToStr(temp, tagOfStructField)
	}
}

func IsTimeExpired(t time.Time) bool {
	return time.Now().After(t)
}

func StToPtr(t time.Time) *time.Time {
	return &t
}

func ValidTimeRange(startTime, endTime *int, defaultTimeLen, maxTimeLen int64) error {
	switch {
	case *startTime == 0 && *endTime == 0:
		*endTime = int(time.Now().Unix())
		*startTime = *endTime - int(defaultTimeLen)
	case *endTime > 0 && *startTime == 0:
		*startTime = *endTime - int(defaultTimeLen)
	case *startTime > 0 && *endTime == 0:
		*endTime = common.IfInt(
			*startTime+int(defaultTimeLen) >
				int(time.Now().Unix()), int(time.Now().Unix()), *startTime+int(defaultTimeLen))
	case *startTime > 0 && *endTime > 0:
		*endTime = common.IfInt(*endTime > int(time.Now().Unix()), int(time.Now().Unix()), *endTime)
	}
	if *endTime <= 0 || *startTime <= 0 {
		return fmt.Errorf("参数异常(arguments exception)")
	}
	if *endTime <= *startTime {
		return fmt.Errorf("参数异常(arguments exception)")
	}
	if *endTime-*startTime > int(maxTimeLen) {
		return fmt.Errorf("超过时间范围限制(exceeding time length limitation):%d days", maxTimeLen/24*3600)
	}
	return nil
}

func TimeStrIncr(timePar string, days int64) (timeRes string, err error) {
	loc, _ := time.LoadLocation("Local")
	theTime, err := time.ParseInLocation(common.IfStr(len(timePar) > 10, timeFormat, dateFormat), timePar, loc)
	timeRes = time.Unix(theTime.Unix()+86400*days, 0).Format("2006-01-02")
	return
}

func GetBetweenDates(startTime, endTime int) (dates []string, err error) {
	sDate := FormateDate2(time.Unix(int64(startTime), 0))
	eDate := FormateDate2(time.Unix(int64(endTime), 0))
	dates = make([]string, 0)
	timeFormatTpl := "2006-01-02"
	startDate, err := time.Parse(timeFormatTpl, sDate)
	if err != nil {
		return
	}
	endDate, err := time.Parse(timeFormatTpl, eDate)
	if err != nil {
		return
	}
	if startDate.After(endDate) {
		err = fmt.Errorf("参数有误或格式不合法(Arguments is not valid)")
		return
	}
	dateStr := startDate.Format(timeFormatTpl)
	dates = append(dates, dateStr)
	for !startDate.Equal(endDate) {
		startDate = startDate.AddDate(0, 0, 1)
		dateStr = startDate.Format(timeFormatTpl)
		dates = append(dates, dateStr)
	}
	return
}

func GetBetweenTimes(startTime, endTime, unitSize int) (times [][]string, err error) {
	if startTime > endTime {
		return [][]string{}, fmt.Errorf("参数有误或格式不合法(Arguments is not valid)")
	}
	if endTime-startTime <= 24*3600 {
		return [][]string{{TimeStampToStr(int64(startTime)), TimeStampToStr(int64(endTime))}}, nil
	}
	for startTime < endTime {
		e := TimeStampToStrYMD(int64(startTime)) + " 23:59:59"
		if TimeByLocation(e).After(*TimeStampToTime(int64(endTime))) {
			e = TimeStampToStr(int64(endTime))
		}
		times = append(times, []string{TimeStampToStr(int64(startTime)), e})
		startTime = int(TimeByLocation(TimeStampToStrYMD(TimeStampToTime(int64(startTime)).AddDate(0, 0, 1).Unix()) + " 00:00:00").Unix())
	}
	if len(times) > 0 && unitSize > 0 {
		results := [][]string{}
		for _, ones := range array_kit.SplitArray(unitSize, array_kit.ConvertArrayType(times)) {
			temp := []string{}
			for _, ione := range ones {
				if one, ok := ione.([]string); ok {
					temp = append(temp, one...)
				}
			}
			results = append(results, temp)
		}
		return results, nil
	}
	return
}

func FillDates(startTime, endTime int, result map[string]interface{}) error {
	dates, err := GetBetweenDates(startTime, endTime)
	if err != nil {
		return err
	}
	for _, one := range result {
		if oneMap, ok := one.(map[string]interface{}); ok {
			for _, date := range dates {
				if InterfaceIsNil(oneMap[date]) {
					oneMap[date] = 0
				}
			}
		}
	}
	return nil
}

func NextDay() *time.Time {
	return TimeByLocation(FormateDate2(time.Now().AddDate(0, 0, 1)) + " 00:00:00")
}
